package pathfinding

import (
	"math"
)

type Cube struct {
	Q int16
	R int16
	S int16
}

func (cube *Cube) EqualsWith(q int16, r int16, s int16) bool {
	return cube.Q == q && cube.R == r && cube.S == s
}
func (cube *Cube) Fix() {
	cube.S = -cube.Q - cube.R
}

func (cube *Cube) Offset(q int16, r int16, s int16) Cube {
	return Cube{Q: cube.Q + q, R: cube.R + r, S: cube.S + s}
}
func (cube *Cube) Move(hex *Cube) {
	cube.Q += hex.Q
	cube.R += hex.R
	cube.S += hex.S
}

func (cube *Cube) ToEvenQ() Vector2 {
	var col = cube.Q
	var row = cube.R + (int16)((cube.Q+(cube.Q&1))/2)
	return Vector2{col, row}
}

func (cube *Cube) ToOddQ() Vector2 {
	var col = cube.Q
	var row = cube.R + (int16)((cube.Q-(cube.Q&1))/2)
	return Vector2{col, row}
}

func (cube *Cube) ToEvenR() Vector2 {
	var col = cube.Q + (int16)((cube.R+(cube.R&1))/2)
	var row = cube.R
	return Vector2{col, row}
}

func (cube *Cube) ToOddR() Vector2 {
	var col = cube.Q + (int16)((cube.R-(cube.R&1))/2)
	var row = cube.R
	return Vector2{col, row}
}
func (cube *Cube) Equals(other *Cube) bool {
	return cube.Q == other.Q && cube.R == cube.R
}

func DistanceWithCube(a *Cube, b *Cube) int {
	return int(math.Abs(float64(a.Q-b.Q))+math.Abs(float64(a.R-b.R))+math.Abs(float64(a.S-b.S))) / 2
}
func ToDirection(source *Cube, target *Cube) byte {
	var a = CubeDirectionMap[target.Q-source.Q+1][target.R-source.R+1]
	if a == -1 {
		panic("cube direction")
	}
	return byte(a)
}
func Offset(target *Cube, other *Cube) Cube {
	return Cube{target.Q + other.Q, target.R + other.R, target.S + other.S}
}
func FromDirection(direction int /* 0 to 5 */) Cube {
	return CubeDirection[direction]
}

func Neighbor(hex *Cube, direction int) Cube {
	c := FromDirection(direction)
	return Offset(hex, &c)
}

func Scale(direction int, distance int) Cube {
	cube := Cube{}
	d := FromDirection(direction)
	for i := 0; i < distance; i++ {
		cube.Move(&d)
	}
	return cube
}

func HexRangeDistance(cube *Cube, distance int) []Cube {
	a := []Cube{}
	for q := -distance; q <= distance; q++ {
		r1 := math.Max(float64(-distance), float64(-q-distance))
		r2 := math.Min(float64(distance), float64(-q+distance))
		for r := int(r1); r <= int(r2); r++ {
			v := cube.Offset(int16(q), int16(r), int16(-q-r))
			a = append(a, v)
		}
	}

	return a
}

func HexRangeDistanceFunc(cube *Cube, distance int, fn func(c *Cube) bool) {
	for q := -distance; q <= distance; q++ {
		r1 := math.Max(float64(-distance), float64(-q-distance))
		r2 := math.Min(float64(distance), float64(-q+distance))
		for r := int(r1); r <= int(r2); r++ {
			v := cube.Offset(int16(q), int16(r), int16(-q-r))
			if !fn(&v) {
				return
			}
		}
	}
}

func HexRingDistance(cube *Cube, distance int, direction int) []Cube {
	a := []Cube{}
	s := Scale(direction, distance)
	c := Offset(cube, &s)
	for i := 0; i < 6; i++ {
		for j := 0; j < distance; j++ {
			a = append(a, c)
			c = Neighbor(&c, i)
		}
	}
	return a
}

func HexRingDistanceFunc(cube *Cube, distance int, direction int, fn func(c *Cube) bool) {
	s := Scale(4, distance)
	c := Offset(cube, &s)
	for i := 0; i < 6; i++ {
		for j := 0; j < distance; j++ {
			c = Neighbor(&c, i)
			if !fn(&c) {
				return
			}
		}
	}
}
